using System;

namespace Chaos.NaCl.Internal.Ed25519Ref10;

internal static partial class FieldOperations
{
    /*
    Preconditions:
      |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

    Write p=2^255-19; q=floor(h/p).
    Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).

    Proof:
      Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
      Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4.

      Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
      Then 0<y<1.

      Write r=h-pq.
      Have 0<=r<=p-1=2^255-20.
      Thus 0<=r+19(2^-255)r<r+19(2^-255)2^255<=2^255-1.

      Write x=r+19(2^-255)r+y.
      Then 0<x<2^255 so floor(2^(-255)x) = 0 so floor(q+2^(-255)x) = q.

      Have q+2^(-255)x = 2^(-255)(h + 19 2^(-25) h9 + 2^(-1))
      so floor(2^(-255)(h + 19 2^(-25) h9 + 2^(-1))) = q.
    */
    internal static void fe_tobytes(byte[] s, int offset, ref FieldElement h)
    {
        FieldElement hr;
        fe_reduce(out hr, ref h);

        Int32 h0 = hr.x0;
        Int32 h1 = hr.x1;
        Int32 h2 = hr.x2;
        Int32 h3 = hr.x3;
        Int32 h4 = hr.x4;
        Int32 h5 = hr.x5;
        Int32 h6 = hr.x6;
        Int32 h7 = hr.x7;
        Int32 h8 = hr.x8;
        Int32 h9 = hr.x9;

        /*
        Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
        Have h0+...+2^230 h9 between 0 and 2^255-1;
        evidently 2^255 h10-2^255 q = 0.
        Goal: Output h0+...+2^230 h9.
        */
        unchecked
        {
            s[offset + 0] = (byte) (h0 >> 0);
            s[offset + 1] = (byte) (h0 >> 8);
            s[offset + 2] = (byte) (h0 >> 16);
            s[offset + 3] = (byte) ((h0 >> 24) | (h1 << 2));
            s[offset + 4] = (byte) (h1 >> 6);
            s[offset + 5] = (byte) (h1 >> 14);
            s[offset + 6] = (byte) ((h1 >> 22) | (h2 << 3));
            s[offset + 7] = (byte) (h2 >> 5);
            s[offset + 8] = (byte) (h2 >> 13);
            s[offset + 9] = (byte) ((h2 >> 21) | (h3 << 5));
            s[offset + 10] = (byte) (h3 >> 3);
            s[offset + 11] = (byte) (h3 >> 11);
            s[offset + 12] = (byte) ((h3 >> 19) | (h4 << 6));
            s[offset + 13] = (byte) (h4 >> 2);
            s[offset + 14] = (byte) (h4 >> 10);
            s[offset + 15] = (byte) (h4 >> 18);
            s[offset + 16] = (byte) (h5 >> 0);
            s[offset + 17] = (byte) (h5 >> 8);
            s[offset + 18] = (byte) (h5 >> 16);
            s[offset + 19] = (byte) ((h5 >> 24) | (h6 << 1));
            s[offset + 20] = (byte) (h6 >> 7);
            s[offset + 21] = (byte) (h6 >> 15);
            s[offset + 22] = (byte) ((h6 >> 23) | (h7 << 3));
            s[offset + 23] = (byte) (h7 >> 5);
            s[offset + 24] = (byte) (h7 >> 13);
            s[offset + 25] = (byte) ((h7 >> 21) | (h8 << 4));
            s[offset + 26] = (byte) (h8 >> 4);
            s[offset + 27] = (byte) (h8 >> 12);
            s[offset + 28] = (byte) ((h8 >> 20) | (h9 << 6));
            s[offset + 29] = (byte) (h9 >> 2);
            s[offset + 30] = (byte) (h9 >> 10);
            s[offset + 31] = (byte) (h9 >> 18);
        }
    }

    internal static void fe_reduce(out FieldElement hr, ref FieldElement h)
    {
        Int32 h0 = h.x0;
        Int32 h1 = h.x1;
        Int32 h2 = h.x2;
        Int32 h3 = h.x3;
        Int32 h4 = h.x4;
        Int32 h5 = h.x5;
        Int32 h6 = h.x6;
        Int32 h7 = h.x7;
        Int32 h8 = h.x8;
        Int32 h9 = h.x9;
        Int32 q;
        Int32 carry0;
        Int32 carry1;
        Int32 carry2;
        Int32 carry3;
        Int32 carry4;
        Int32 carry5;
        Int32 carry6;
        Int32 carry7;
        Int32 carry8;
        Int32 carry9;

        q = (19 * h9 + (((Int32)1) << 24)) >> 25;
        q = (h0 + q) >> 26;
        q = (h1 + q) >> 25;
        q = (h2 + q) >> 26;
        q = (h3 + q) >> 25;
        q = (h4 + q) >> 26;
        q = (h5 + q) >> 25;
        q = (h6 + q) >> 26;
        q = (h7 + q) >> 25;
        q = (h8 + q) >> 26;
        q = (h9 + q) >> 25;

        /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */
        h0 += 19 * q;
        /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */

        carry0 = h0 >> 26; h1 += carry0; h0 -= carry0 << 26;
        carry1 = h1 >> 25; h2 += carry1; h1 -= carry1 << 25;
        carry2 = h2 >> 26; h3 += carry2; h2 -= carry2 << 26;
        carry3 = h3 >> 25; h4 += carry3; h3 -= carry3 << 25;
        carry4 = h4 >> 26; h5 += carry4; h4 -= carry4 << 26;
        carry5 = h5 >> 25; h6 += carry5; h5 -= carry5 << 25;
        carry6 = h6 >> 26; h7 += carry6; h6 -= carry6 << 26;
        carry7 = h7 >> 25; h8 += carry7; h7 -= carry7 << 25;
        carry8 = h8 >> 26; h9 += carry8; h8 -= carry8 << 26;
        carry9 = h9 >> 25; h9 -= carry9 << 25;
        /* h10 = carry9 */

        hr.x0 = h0;
        hr.x1 = h1;
        hr.x2 = h2;
        hr.x3 = h3;
        hr.x4 = h4;
        hr.x5 = h5;
        hr.x6 = h6;
        hr.x7 = h7;
        hr.x8 = h8;
        hr.x9 = h9;
    }
}